/*   Carina
 *   Copyright (C) 2009 2010 2011  Zdravko Velinov
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "scene-explorer.hh"

#include "carina/math.hh"
#include "carina/ces-loader.hh"
#include "carina/common/memory.hh"

#include <stack>
#include <limits>

#include <QTreeWidget>
#include <QMessageBox>
#include <QColorDialog>
#include <QMenu>

STNode::STNode(STNode* _parent, CESNode& _ces_node, const NodePtr& _carina_node)
    :   m_CESNode(_ces_node),
        m_CarinaNode(_carina_node),
        m_Parent(_parent) {}

STNode::~STNode()
{
    for(size_t i = 0; i < m_Children.size(); ++i)
        CE_DEALLOCATE(m_Children[i]);
}

size_t STNode::getChildrenCount() 
{
    return m_Children.size();
}

STNode* STNode::getParent()
{
    return m_Parent;
}

#undef max
size_t STNode::getIndex(STNode* child)
{
    for(size_t i = 0; i < m_Children.size(); ++i)
        if(child == m_Children[i])
            return i;
    return std::numeric_limits<size_t>::max();
}

STNode& STNode::getChild(size_t idx)
{
    return *m_Children[idx];
}

CESNode& STNode::getCESNode()
{
    return m_CESNode;
}

NodePtr STNode::getCarinaNode()
{
    return m_CarinaNode;
}

void STNode::deleteChildren(size_t lhs, size_t rhs)
{
    for(size_t i = lhs; i < rhs; ++i)
        CE_DEALLOCATE(m_Children[i]);
    m_Children.erase(lhs, rhs);
}

void STNode::clear()
{
    for(size_t i = 0; i < m_Children.size(); ++i)
        CE_DEALLOCATE(m_Children[i]);
    m_Children.clear();
}

void STNode::addChild(CESNode& _ces_node, const NodePtr& _carina_node)
{
    m_Children.push_back(CE_ALLOCATE(STNode)(this, _ces_node, _carina_node));
}

SceneExplorer::SceneExplorer(CESFile& ces_file, const LightingManagerPtr& lmgr, const NodeManagerPtr& nmgr, const ScenePtr& scene)
    :   m_STModel(ces_file, nmgr, scene),
        m_GLSModel(ces_file.getGlobalLightSection(), lmgr, scene),
        m_CESFile(ces_file),
        m_Scene(scene),
        m_NodeMgr(nmgr),
        m_LightMgr(lmgr)
{
    m_SceneExp.setupUi(this);

    m_SceneExp.SceneTree->setModel(&m_STModel);

    Vector3 amb_color(scene->getAmbientLight());
    QColor qamb_color;
    qamb_color.setRgbF(amb_color.r(), amb_color.g(), amb_color.b());
    m_SceneExp.AmbientColorPicker->setColor(qamb_color);

    m_SceneExp.GlobalLightList->setModel(&m_GLSModel);
    m_SceneExp.GlobalLightList->setContextMenuPolicy(Qt::CustomContextMenu);
}

bool SceneExplorer::reload()
{
    bool result = m_STModel.reload() && m_GLSModel.reload();
    vec3_t amb_light;
    QColor qamb_color;
    m_CESFile.getGlobalLightSection().getAmbientLight(amb_light);
    qamb_color.setRgbF(amb_light.coordinate.x,
                       amb_light.coordinate.y,
                       amb_light.coordinate.z);
    m_SceneExp.AmbientColorPicker->setColor(qamb_color);
    m_Scene->setAmbientLight(*reinterpret_cast<Vector3*>(&amb_light));


    if(m_CurrentEditable)
    {
        m_CurrentEditable = EditablePtr();
        emit editableSelected(m_CurrentEditable);
    }

    return result;
}

void SceneExplorer::clear()
{
    m_CESFile.getGlobalLightSection().setAmbientLight(vec3_zero);
    m_STModel.clear();
    m_GLSModel.clear();
    if(m_CurrentEditable)
    {
        m_CurrentEditable = EditablePtr();
        emit editableSelected(m_CurrentEditable);
    }
}

void SceneExplorer::on_AddNode_clicked(bool checked)
{
    QModelIndex parent = getSelected();
    
    m_STModel.insertNode(parent);
}

void SceneExplorer::on_DeleteObject_clicked(bool checked)
{
    QItemSelectionModel* selection_model = m_SceneExp.SceneTree->selectionModel();
    QModelIndexList midx_list = selection_model->selectedRows();
    QList<QPersistentModelIndex> pmidx_list;
    for(QModelIndexList::const_iterator i = midx_list.begin(); i != midx_list.end(); ++i)
        pmidx_list.append(QPersistentModelIndex(*i));
    for(QList<QPersistentModelIndex>::iterator i = pmidx_list.begin(); i != pmidx_list.end(); ++i)
    {
        STNode& st_node = STNodeReference(*i);
        if(&st_node.getCESNode().attributes() == &m_CurrentEditable->m_CESAttributes)
        {
            m_CurrentEditable = EditablePtr();
            emit editableSelected(m_CurrentEditable);
        }
        m_STModel.removeRow(i->row(), i->parent());
    }
}

void SceneExplorer::on_AmbientColorPicker_clicked()
{
    QColorDialog color_dlg(this);
    if(color_dlg.exec() == QDialog::Accepted)
    {
        qreal v[3];
        QColor color(color_dlg.currentColor());
        m_SceneExp.AmbientColorPicker->setColor(color);
        color.getRgbF(v, v+1, v+2);
        Vector3 amb_color((float)v[0], (float)v[1], (float)v[2]);
        m_Scene->setAmbientLight(amb_color);
        m_CESFile.getGlobalLightSection().setAmbientLight(*reinterpret_cast<vec3_t*>(&amb_color));
        Vector3 amb_res;
        m_CESFile.getGlobalLightSection().getAmbientLight(*reinterpret_cast<vec3_t*>(&amb_res));
        assert(amb_color == amb_res);
    }
}

QModelIndex SceneExplorer::getSelected()
{
    QItemSelectionModel* selection_model = m_SceneExp.SceneTree->selectionModel();
    QModelIndexList midx_list = selection_model->selectedRows();
    if(midx_list.size() > 1)
    {
        QMessageBox::information(this, "Add node", "Ambiguous parent node. You must select only one node.");
        return QModelIndex();
    }
    else if(midx_list.empty())
        return m_STModel.index(0, 0);
    
    return midx_list[0];
}

void SceneExplorer::createLightSource(const string& name)
{
    LightSourcePtr lsource = m_LightMgr->create(name);
    if(lsource->isGlobalLightSource())
        m_GLSModel.insertRow(name, static_pointer_cast<GlobalLightSource>(lsource));
    else
    {
        LightSourceNodePtr lsnode = make_aligned_shared<LightSourceNode>(static_pointer_cast<SpatialLightSource>(lsource));
        QModelIndex parent = getSelected();
        if(parent.isValid())
            m_STModel.insertRow("Light source node", name, lsnode, parent);
    }
}

void SceneExplorer::createModelInstance(const string& model_inst)
{
    NodePtr node = m_NodeMgr->create("Model node", model_inst);
    QModelIndex parent = getSelected();
    if(parent.isValid())
        m_STModel.insertRow("Model node", model_inst, node, parent);
}

void SceneExplorer::on_SceneTree_clicked(const QModelIndex& index)
{
    m_CurrentEditable = m_STModel.getEditable(index);
    if(!m_CurrentEditable)
        return;
    emit editableSelected(m_CurrentEditable);
}

void SceneExplorer::on_GlobalLightList_clicked(const QModelIndex& index)
{
    m_CurrentEditable = m_GLSModel.getEditable(index);
    if(!m_CurrentEditable)
        return;
    emit editableSelected(m_CurrentEditable);
}

void SceneExplorer::on_GlobalLightList_customContextMenuRequested(const QPoint& pos)
{
    QModelIndex idx = m_SceneExp.GlobalLightList->indexAt(pos);
    if(!idx.isValid())
        return;

    QMenu menu(this);
    QAction* del_gls = menu.addAction("Delete Light Source");
    if(menu.exec(QCursor::pos()) == del_gls)
    {
        if(&m_CurrentEditable->m_CESAttributes == &m_CESFile.getGlobalLightSection().getGlobalLight(idx.row()))
        {
            m_CurrentEditable = EditablePtr();
            emit editableSelected(m_CurrentEditable);
        }
        m_GLSModel.removeRow(idx.row());
    }
}

SceneTreeModel::SceneTreeModel(CESFile& ces_file, const NodeManagerPtr& nmgr, const ScenePtr& scene)
    :   m_Root(0, ces_file.getRoot(), scene->getRoot()),
        m_Scene(scene),
        m_NodeMgr(nmgr)
{
}
    
QModelIndex SceneTreeModel::parent(const QModelIndex& index) const
{
    if(index.isValid())
    {
        STNode& node = STNodeReference(index);
        STNode* parent = node.getParent();
        if(!parent)
            return QModelIndex();

        STNode* pp = parent->getParent();
        int row = pp ? pp->getIndex(parent) : 0;
        return createIndex(row, 0, parent);
    }
    return QModelIndex();
}

QModelIndex SceneTreeModel::index(int row, int column, const QModelIndex& parent) const
{
    if(parent.isValid())
    {
        STNode& parent_node = STNodeReference(parent);
        if(row >= (int)parent_node.getChildrenCount() || column >= ATTR_COUNT)
            return QModelIndex();
        return createIndex(row, column, &parent_node.getChild(row));
    }
    
    if(row != 0 || column >= ATTR_COUNT)
        return QModelIndex();

    return createIndex(0, column, const_cast<STNode*>(&m_Root));
}

QVariant SceneTreeModel::data(const QModelIndex& index, int role) const
{
    if(role == Qt::DisplayRole && index.isValid())
    {
        STNode& node = STNodeReference(index);
        switch(index.column())
        {
        case NODE_TYPE:
        {
            string node_type;
            node.getCESNode().attributes().getAttribute("Node type", node_type);
            return QVariant(QString(node_type.c_str()).toLower());
        }
        default:
            return QVariant();
        }
    }
    return QVariant();
}

EditablePtr SceneTreeModel::getEditable(const QModelIndex& index)
{
    if(!index.isValid())
        return EditablePtr();
    STNode& node = STNodeReference(index);
    return make_aligned_shared<Editable>(node.getCESNode().attributes(), node.getCarinaNode()->getConfigurable());
}

int SceneTreeModel::rowCount(const QModelIndex& parent) const
{
    return parent.isValid() ? STNodeReference(parent).getChildrenCount() : 1;
}

int SceneTreeModel::columnCount(const QModelIndex & parent) const
{
    return ATTR_COUNT;
}

bool SceneTreeModel::insertRows(int row, int count, const QModelIndex& parent)
{
    return false;
}

bool SceneTreeModel::removeRows(int row, int count, const QModelIndex& parent)
{
    if(!count)
        return false;
    int row_end = row + count;
    if(!parent.isValid())
        return false;
    STNode& parent_node = STNodeReference(parent);
    if(count > (int)parent_node.getChildrenCount())
        return false;

    beginRemoveRows(parent, row, row_end - 1);
    parent_node.deleteChildren(row, row_end);
    static_cast<TransformNode*>(parent_node.getCarinaNode().get())->removeChildren(row, row_end);
    parent_node.getCESNode().removeChildren(row, row_end);
    endRemoveRows();
    return true;
}

bool SceneTreeModel::insertRow(const string& node_type, const string& creation_data, const NodePtr& carina_node, const QModelIndex& parent)
{
    if(!parent.isValid())
        return false;

    STNode& parent_node = STNodeReference(parent);
    int     row_idx = parent_node.getChildrenCount();
    beginInsertRows(parent, row_idx, row_idx);
    CESNode& ces_node = parent_node.getCESNode().addChild();
    ces_node.attributes().setAttribute("Node type", node_type);
    ces_node.attributes().setAttribute("Creation data", creation_data);
    if(!CESLoader::transferAttributesToCES(*carina_node->getConfigurable(), ces_node.attributes()))
        return false;
    static_cast<TransformNode*>(parent_node.getCarinaNode().get())->addChild(carina_node);
    parent_node.addChild(ces_node, carina_node);
    endInsertRows();
    return true;
}

bool SceneTreeModel::insertNode(const QModelIndex& parent)
{
    if(!parent.isValid())
        return false;

    STNode& parent_node = STNodeReference(parent);
    int     row_idx = parent_node.getChildrenCount();
    beginInsertRows(parent, row_idx, row_idx);
    
    TransformNodePtr carina_node = make_aligned_shared<TransformNode>();
    CESNode& ces_node = parent_node.getCESNode().addChild();
    ces_node.attributes().setAttribute("Node type", "Transform node");
    if(!CESLoader::transferAttributesToCES(*carina_node->getConfigurable(), ces_node.attributes()))
        return false;
    static_cast<TransformNode*>(parent_node.getCarinaNode().get())->addChild(carina_node);
    parent_node.addChild(ces_node, carina_node);
    endInsertRows();
    return true;
}

void SceneTreeModel::clear()
{
    beginResetModel();
    m_Scene->getRoot()->clear();
    m_Root.clear();
    m_Root.getCESNode().clear();
    endResetModel();
}

struct CESLoaderStack
{
    STNode          &parent;
    CESNode         &node;

    CESLoaderStack(STNode& _parent, CESNode& _node)
        :   parent(_parent),
            node(_node) {}
};

bool SceneTreeModel::reload()
{
    bool result = true;
    std::queue<CESLoaderStack> stack;
    beginResetModel();
    m_Root.clear();
    m_Scene->getRoot()->clear();
    for(size_t i = 0; i < m_Root.getCESNode().getChildrenCount(); ++i)
        stack.push(CESLoaderStack(m_Root, m_Root.getCESNode().getChild(i)));
    while(!stack.empty())
    {
        string node_type;
        CESNode& cur_node = stack.front().node;
        STNode& parent_node = stack.front().parent;
        stack.pop();

        NodePtr carina_node = CESLoader::loadNode(cur_node, m_NodeMgr);
        if(!carina_node)
        {
            result = false;
            break;
        }
        static_cast<TransformNode*>(parent_node.getCarinaNode().get())->addChild(carina_node);
        parent_node.addChild(cur_node, carina_node);

        cur_node.attributes().getAttribute("Node type", node_type);
        if(node_type == "Transform node")
        {
            for(size_t i = 0; i < cur_node.getChildrenCount(); ++i)
                stack.push(CESLoaderStack(parent_node.getChild(parent_node.getChildrenCount()-1), cur_node.getChild(i)));
        }
    }
    endResetModel();
    return result;
}

GLSModel::GLSModel(CESGlobalLightSection& glight_section, const LightingManagerPtr& lmgr, const ScenePtr& scene)
    :   m_Scene(scene),
        m_GLightSection(glight_section),
        m_LightMgr(lmgr)
{
}

int GLSModel::rowCount(const QModelIndex& parent) const
{
    return m_Scene->getGlobalLightSourceCount();
}

QVariant GLSModel::data(const QModelIndex& index, int role) const
{
    if(role == Qt::DisplayRole && index.isValid())
    {
        switch(index.column())
        {
        case LIGHT_SOURCE_TYPE:
        {
            string lstype;
            m_GLightSection.getGlobalLight(index.row()).getAttribute("Light source type", lstype);
            return QVariant(QString(lstype.c_str()).toLower());
        }
        default:
            return QVariant();
        }
    }
    return QVariant();
}

EditablePtr GLSModel::getEditable(const QModelIndex& index)
{
    if(!index.isValid())
        return EditablePtr();
    if(index.row() >= (int)m_GLightSection.getGlobalLightCount())
        return EditablePtr();
    GlobalLightSourcePtr gls(m_Scene->getGlobalLightSource(index.row()));
    CESAttributeContainer& ces_attr = m_GLightSection.getGlobalLight(index.row());
    return make_aligned_shared<Editable>(ces_attr, gls);
}

bool GLSModel::insertRows(int row, int count, const QModelIndex &index)
{
    return false;
}

bool GLSModel::removeRows(int row, int count, const QModelIndex &parent)
{
    int row_end = row + count;
    beginRemoveRows(parent, row, row_end - 1);
    m_Scene->removeGlobalLightSources(row, row_end);
    m_GLightSection.removeGlobalLight(row, row_end);
    endRemoveRows();
    return true;
}

bool GLSModel::insertRow(const string& lstype, const GlobalLightSourcePtr& gls)
{
    beginInsertRows(QModelIndex(), m_Scene->getGlobalLightSourceCount(), m_Scene->getGlobalLightSourceCount()+1);
    CESAttributeContainer& attr_cont = m_GLightSection.addGlobalLight();
    attr_cont.setAttribute("Light source type", lstype);
    CESLoader::transferAttributesToCES(*gls, attr_cont);
    m_Scene->addGlobalLightSource(gls);
    endInsertRows();
    return true;
}

bool GLSModel::reload()
{
    bool result = true;
    beginResetModel();
    for(size_t i = 0; i < m_GLightSection.getGlobalLightCount(); ++i)
    {
        GlobalLightSourcePtr gls = CESLoader::loadGlobalLightSource(m_GLightSection.getGlobalLight(i), m_LightMgr);
        if(!gls)
        {
            result = false;
            break;
        }
        m_Scene->addGlobalLightSource(gls);
    }
    endResetModel();
    return result;
}

void GLSModel::clear()
{
    beginResetModel();
    m_Scene->clearGlobalLightSources();
    endResetModel();
}
